import fs from 'fs-extra';
import _, { get } from 'lodash';

interface IYamlOption {
    indent: string;
}

const enum ENodeType {
    None,
    String,
    Object,
    Array
}

class NodeModifier {
    private type: ENodeType = ENodeType.None;
    private value: string | object | null = null;
    private children: NodeModifier[] = [];
    constructor(private lines: string[], private start: number, private end: number, private deep: number, private key: string, private option: IYamlOption) {
        let type = ENodeType.None;
        const mch = lines[start].match(/\s*\w+:(.*)/);
        const tail = mch![1].trim();
        if (tail[0] == '{') {
            type = ENodeType.Object;
            this.value = JSON.parse(tail);
        } else if (tail[0] == '[') {
            type = ENodeType.Array;
        }

        if (type == ENodeType.None) {
            for (let i = start + 1; i <= end; i++) {
                const line = lines[i];
                if (line.trim()[0] == '-') {
                    type = ENodeType.Array;
                    break;
                }
            }
        }
        if (type == ENodeType.None) {
            if (end == start) {
                // todo 此处没考虑多行字符串的情况
                type = ENodeType.String;
                this.value = tail;
            } else {
                type = ENodeType.Object;
            }
        }
        this.type = type;
    }

    getNode(key: string): NodeModifier | null {
        // todo: 暂不支持单行object的情况
        const indent = _.repeat(this.option.indent, this.deep + 1);
        const keywords1 = `${indent}${key}:`, keywords2 = _.repeat(this.option.indent, this.deep) + `- ${key}`;
        let ns = -1, ne = -1;
        for (let i = this.start; i <= this.end; i++) {
            const line = this.lines[i];
            if (ns < 0) {
                if (line.startsWith(keywords1) || line.startsWith(keywords2)) {
                    ns = i, ne = i;
                }
            } else {
                if (line[indent.length] != ' ' && line[indent.length] != '-') {
                    break;
                }
                ne++;
            }
        }
        if (ns < 0) return null;
        return new NodeModifier(this.lines, ns, ne, this.deep + 1, key, this.option);
    }

    getNodeOrThrow(key: string): NodeModifier {
        const node = this.getNode(key);
        if (node == null) throw new Error(`can not find node ${key}: ${this.start} - ${this.end}`);
        return node;
    }

    getValue(): string {
        if (this.type != ENodeType.String) throw new Error(`node is not string: ${this.start} - ${this.end}`);
        return this.value as string;
    }

    setValue(value: string) {
        if (this.type!= ENodeType.String) throw new Error(`node is not string: ${this.start} - ${this.end}`);
        this.value = value;
        this.lines[this.start] = _.repeat(this.option.indent, this.deep) + `${this.key}: ${this.value}`;
    }

    getArraySubNodes(): NodeModifier[] {
        if (this.type != ENodeType.Array) return [];
        const out: NodeModifier[] = [];
        const indent = _.repeat(this.option.indent, this.deep);
        let sn: NodeModifier | null = null;
        for (let i = this.start + 1; i <= this.end; i++) {
            if (this.lines[i].startsWith(indent + '- ')) {
                sn = new NodeModifier(this.lines, i, i, this.deep, '__ARRAYNODE__', this.option);
                out.push(sn);
            } else {
                if (sn) {
                    sn.end = i;
                    sn = null;
                }
            }
        }
        return out;
    }

    addArraySubNode(o: Record<string, string>) {
        if (this.type!= ENodeType.Array) throw new Error(`node is not array: ${this.start} - ${this.end}`);
        const joins = '\n' + _.repeat(this.option.indent, this.deep + 1);
        const s = _.repeat(this.option.indent, this.deep) + '- ' + Object.keys(o).map(k=>`${k}: ${o[k]}`).join(joins);
        this.lines[this.start] = this.lines[this.start].replace(/: \[\]$/, ':\n' + s);
    }
}

export class YamlModifier {
    private lines: string[] = [];
    private node: NodeModifier;
    constructor(private file: string, private option: IYamlOption) {
        this.lines = fs.readFileSync(file, 'utf-8').split(/\r?\n/);
        let sn = 0;
        if (this.lines[0].startsWith('%YAML')) {
            if (this.lines[1].startsWith('%TAG') && this.lines[2].startsWith('---')) {
                sn = 3;
            } else {
                throw new Error('invalid yaml file');
            }
        }
        this.node = new NodeModifier(this.lines, sn, this.lines.length - 1, -1, '__DOC__', option);
    }

    get modifier(): NodeModifier { 
        return this.node;
    }

    save(): void {
        fs.writeFileSync(this.file, this.lines.join('\n'));
    }
}
